package model.parser;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import resource.ResourceManager;
import state.TabState;
import state.TurtleState;
import model.expression.AbstractExpression;
import model.expression.command.AbstractCommandExpression;
import model.tokens.AbstractToken;
import model.tokens.CommandToken;
import model.tokens.OperatorToken;
import model.tokens.TokenFactory;
import model.tokens.VariableToken;
import expression.Expression;

/**
 * Working version of a parser. It will create List of parsingData from a resource bundle,
 * and use it to parse string inputs
 * 
 * We will attempt to make this more general
 * 
 * @author KevinWang Chris James Misha Lavrov, DJ Sharkey, LingZhao Xie
 *
 * @param <T> any object
 */
public class Parser
{
	TokenFactory tokenFactory;
    private List<ParsingData> parsingData;
	public String commandRegEx;
    private static final String resourceSource ="parsingdata";


    public Parser(){
    	 try
         {
             this.parsingData = getParsingData(resourceSource);
         }
         catch (Exception e)
         {
             throw ParserException.BAD_REFLECTION;
         }
     	this.tokenFactory=new TokenFactory(this.parsingData);

    }
    
    /**
     * 
     * @param resourceSource
     * @return creates a list of ParsingData objects which represent the available syntax
     * @throws NumberFormatException
     * @throws ClassNotFoundException
     */
    private List<ParsingData> getParsingData(String resourceSource) throws NumberFormatException, ClassNotFoundException
	{
		ResourceManager rm = ResourceManager.getInstance();
		rm.addResourcesFromFile(resourceSource);
		String[] operatorKeys= rm.getStringArray("tokens");
		ArrayList<ParsingData> parsingDataList = new ArrayList<ParsingData>();
		
		StringBuilder cRegEx = new StringBuilder();

		
        for(String key : operatorKeys)
        {
        	//prep work
            String symbol = rm.getString(key+".symbol");
            int priority = rm.getInteger(key+".priority");
            int numArgs = rm.getInteger(key+".args");
            boolean isFunction = rm.getBoolean(key+".function");
            boolean isLeftAssoc = rm.getBoolean(key+".leftAssoc");
            String tokenClass = rm.getString(key+".token");
            String exprClass = rm.getString(key+".expr");

            //create the object and add to the list
            ParsingData op = new ParsingData(symbol, priority, numArgs,
			                                 isFunction, isLeftAssoc,
			                                 tokenClass, exprClass);
			
			parsingDataList.add(op);
			
			//commandTokens need to parsed in a special way.
			if(tokenClass.equals("model.tokens.CommandToken")){
				String[] symbs = symbol.split("\\|");
				for(String s: symbs){
					cRegEx.append("(?<=(");
					cRegEx.append(s);
					cRegEx.append("))|(?=(");
					cRegEx.append(s);
					cRegEx.append("))|");
				}
			}
			if (tokenClass.equals("model.tokens.LeftBrac") || tokenClass.equals("model.tokens.RightBrac")){
				cRegEx.append("(?<=(");
				cRegEx.append(symbol);
				cRegEx.append("))|(?=(");
				cRegEx.append(symbol);
				cRegEx.append("))|");
			}
        }
        
		this.commandRegEx=cRegEx.substring(0,cRegEx.length()-1).toString();
		return parsingDataList;
	}
    
	public List<AbstractExpression> parse(String input,TabState tabSt) throws ParserException
    {
		//initialize
		Stack<String> msgStack = new Stack<String>();
		Stack<AbstractExpression> out = new Stack<AbstractExpression>();
		
		//split the input before and after command keywords
		String[] inputSplit= input.split(this.commandRegEx);
		
		//msgStack contains the message segments that must be parsed
		msgStack.addAll(Arrays.asList(inputSplit));
		
		//parsre each message segment
		while(!msgStack.empty()){
			//regex is a bit messy and leaves empty segments, ignore them
			if(msgStack.peek().equals(" ")||msgStack.peek().equals("")){
				msgStack.pop();
				continue;
			}
			
			//convert the msgSegment into tokens
			List<AbstractToken> toks = this.tokenFactory.tokenize(msgStack.pop());
			
			//convert this message into expressions.
			Stack<AbstractExpression> outStack=toks.get(0).makeExpression(toks,out,tabSt,this);
			Collections.reverse(outStack);
			
			//add the expressions to theoutput stack.
			out.addAll(outStack);
		}
		
		return new ArrayList<AbstractExpression>(out);
    }

	
}
